Unity解析OSM数据,并生成简单模型 您所在的位置:网站首页 unity 地图模型 Unity解析OSM数据,并生成简单模型

Unity解析OSM数据,并生成简单模型

2023-10-20 15:27| 来源: 网络整理| 查看: 265

文章目录 一、介绍XML数据格式二、Unity解析XML数据格式的方法1.C#自带的方法2.Unity读取TextAsset方法 三、OSM数据介绍四、Unity解析OSM数据1.定义node和way的数据结构2.获取XML文件中node和way的属性值并存储 五、使用LineRender对OSM数据进行简单可视化六、根据OSM数据创建道路和建筑的简单模型1.重新定义node和way的数据结构2.创建模型 七、参考

一、介绍XML数据格式

因为OSM数据就是XML数据格式,所以先介绍一下XML数据格式。

XML 指可扩展标记语言(eXtensible Markup Language)。

XML 被设计用来传输和存储数据。XML 文档形成了一种树结构,它从"根部"开始,然后扩展到"枝叶"。XML 元素指的是从(且包括)开始标签直到(且包括)结束标签的部分。 XML 文档必须包含根元素。该元素是所有其他元素的父元素。所有的元素都可以有文本内容和属性(类似 HTML 中)。在 XML 中,省略关闭标签是非法的。所有元素都必须有关闭标签。

XML 文档实例

Tove Jani Reminder Don't forget me this weekend!

以上显示了根元素note及它的四个子元素(Element)。

二、Unity解析XML数据格式的方法 1.C#自带的方法 //引入命名空间 using System.Xml; public class Parser : MonoBehaviour { //创建xml文档对象 private XmlDocument doc = new XmlDocument(); private void Start() { //读入xml文件 XmlTextReader reader= new XmlTextReader("Assets/" + mapName); //加载到xml文档对象中 doc.Load(reader); //根据元素名称获取元素list XmlNodeList elemList = doc.GetElementsByTagName("node"); //获取元素的子元素们 foreach (XmlNode node in elemList ) { XmlNodeList children = node.ChildNodes; } //获取元素的某一属性名称 string attrName = elemList[0].Attributes[0].Name; //获取元素的某一属性值 string idStr = elemList[0].Attributes["id"].InnerText; //把string转为int int id = int.Parse(idStr); } } 2.Unity读取TextAsset方法

TextAsset是用于导入文本文件的格式。当您将文本文件放入项目文件夹时,它将被转换为TextAsset。支持的文本格式有:.txt、.html、.htm、.xml、.bytes、.json、.csv、.yaml、.fnt。

using System.Xml; [Tooltip("The resource file that contains the OSM map data")] public string resourceFile; [HideInInspector] public Dictionary nodes; void Start() { //读取文件 var txtAsset = Resources.Load(resourceFile); //加载xml内容到xml文件对象 XmlDocument doc = new XmlDocument(); doc.LoadXml(txtAsset.text); //获取单个元素 XmlNode xmlNode = doc.SelectSingleNode("/osm/bounds"); //获取元素list XmlNodeList xmlNodeList = doc.SelectNodes("/osm/node")); } 三、OSM数据介绍

OpenStreetMap的元素(数据基元)主要包括三种:

点(Nodes)路(Ways)关系(Relations)

这三种元素构成了整个地图画面。其中,Nodes定义了空间中点的位置;Ways定义了线或区域;Relations(可选的)定义了元素间的关系。

根元素:

node元素:

可以看到,node元素的属性有:id、lat、lon、user、uid、visible、version、changeset、timestamp。我们实际上需要的只有前三个属性。其他属性的介绍可以看官方介绍。

关于Tag元素:

Tags describe the meaning of the particular element to which they are attached.

简单来说就是用一些键值对表示这些元素的地图特征。并没有一个固定的字典来表示这些tag,但是可以参考这里:Map Features page。

way元素:

每个way包含了多个nd元素。way的属性中我们也只需要id。

relation元素:

relation元素记录了两个或多个数据元素(node、way和/或其他relation)之间的关系。例如:一种路线关系,它列出了组成主要公路、自行车路线或公共汽车路线的路线。relation元素的含义可以有很多,还是要依据tag来看。

四、Unity解析OSM数据 1.定义node和way的数据结构 using System.Collections.Generic; public struct Node { public int id; public float lat, lon; public Node(int ID, float LAT, float LON) { id = ID; lat = LAT; lon = LON; } } public struct Way { public int id; public List wnodes; public Way(int ID) { id = ID; wnodes = new List(); } } 2.获取XML文件中node和way的属性值并存储 private List nodes = new List(); private List ways = new List(); [SerializeField] private string mapName = "map2.osm"; doc.Load(new XmlTextReader("Assets/" + mapName)); //【存储所有nodes】 XmlNodeList elemList = doc.GetElementsByTagName("node"); for (int i = 0; i XmlNodeList wayNodes = node.ChildNodes; //存储way的id ways.Add(new Way(int.Parse(node.Attributes["id"].InnerText))); //存储way的每个node的id foreach (XmlNode nd in wayNodes) { if (nd.Attributes[0].Name == "ref")//根据元素属性筛选出node元素 { ways[ct].wnodes.Add(int.Parse(nd.Attributes["ref"].InnerText)); Debug.Log(ways[ct].wnodes.Count); } } ct++; }

现在nodes和ways两个list已经存储了点和线的id、经纬度信息。

五、使用LineRender对OSM数据进行简单可视化

(1)LineRender介绍:设置LineRender组件的Positions数组,确定一条Line上面控制点的位置。设置width曲线控制Line的宽度。比如现在就是长度为1,宽度约0.2的一条Line。

在这里插入图片描述 在这里插入图片描述 (2)把每条way用LineRender画出来。需要把经纬度转变成xy坐标。

下面这种写法的执行速度很慢,只是体现个思路。

private List wayObjects = new List(); [SerializeField] private float x; [SerializeField] private float y; //在osm文件的bounds元素中已知此区域的经纬度极值 // [SerializeField] private float boundsX = 34; [SerializeField] private float boundsY = -118; for (int i = 0; i foreach (Node nod in nodes)//遍历nodes的list,找到对应node的经纬度 { if (nod.id == ways[i].wnodes[j]) { x = nod.lat; y = nod.lon; } } wayObjects[i].GetComponent().SetPosition(j, new Vector3((x - boundsX) * 100, 0, (y - boundsY) * 100)); } }

效果:

在这里插入图片描述

六、根据OSM数据创建道路和建筑的简单模型

创建简单道路和建筑模型要用到mesh编程的知识。简单来说就是给一游戏物体添加MeshFilter组件后,自己去设置这个Mesh的顶点、法线、三角形、uv。最后结果是这样:

在这里插入图片描述 稍放大一点,可以看到道路上的贴图。

在这里插入图片描述 在wireframe模式下,可以比较清楚的看到,道路是用多个三角形拼起来的,交叉口没有做处理。

建筑除了根据多个点画出底面外,还具备一定的高度。

在这里插入图片描述

1.重新定义node和way的数据结构

因为我们要区分哪些点是用于道路的,哪些点是用于建筑的,所以除了之前的“id”、“lan”、“lon”,我们还需要知道一些Tag的值。

(1)优化一下从xml中获取属性的方法,定义一个BaseOsm类:

using System; using System.Xml; class BaseOsm { protected T GetAttribute(string attrName, XmlAttributeCollection attributes) { string strValue = attributes[attrName].Value; return (T)Convert.ChangeType(strValue, typeof(T)); } }

(2)OSMNode类:

using System.Xml; using UnityEngine; class OsmNode : BaseOsm { public ulong ID {get; private set; } public float Latitude { get; private set; } public float Longitude { get; private set; } public float X { get; private set; } public float Y {get; private set; } // implicit conversion between OsmNode and Vector3 //使得node可以像Vector3一样运算 public static implicit operator Vector3 (OsmNode node) { return new Vector3(node.X, 0, node.Y); } public OsmNode(XmlNode node) { ID = GetAttribute("id", node.Attributes); Latitude = GetAttribute("lat", node.Attributes); Longitude = GetAttribute("lon", node.Attributes); //从经纬度转坐标的方法分离出去了 X = (float)MercatorProjection.lonToX(Longitude); Y = (float)MercatorProjection.latToY(Latitude); } }

(3)OSMWay类:

using System.Collections.Generic; using System.Xml; class OsmWay : BaseOsm { public ulong ID {get; private set; } public bool Visible { get; private set; } public List NodeIDs {get; private set; } public bool IsBoundary {get; private set; }//边界可以在场景中画出,方便判断 public bool IsBuilding {get; private set; } public bool IsRoad {get; private set; } public float Height {get; private set; } public OsmWay(XmlNode node) { NodeIDs = new List(); ID = GetAttribute("id", node.Attributes); Visible = GetAttribute("visible", node.Attributes); //保存way中的nodes的id XmlNodeList nds = node.SelectNodes("nd"); foreach (XmlNode n in nds) { ulong refNo = GetAttribute("ref", n.Attributes); NodeIDs.Add(refNo); } //判断是否是边界 if (NodeIDs.Count > 1) { IsBoundary = NodeIDs[0] == NodeIDs[NodeIDs.Count - 1]; } Height = 10.0f; XmlNodeList tags = node.SelectNodes("tag"); foreach (XmlNode t in tags) { string key = GetAttribute("k", t.Attributes); if (key == "height")//不确定是建筑还是道路高度 { Height = 0.3048f * GetAttribute("v", t.Attributes); } else if (key == "building:levels")//建筑层数,每一层是3m { Height = 3.0f * GetAttribute("v", t.Attributes); } else if (key == "building")//是建筑 { IsBuilding = GetAttribute("v", t.Attributes) == "yes"; Height = 10.0f; } else if (key == "highway")//是公路 { IsRoad = true; } /** would preferably like to use only: ** trunk roads ** primary roads ** secondary roads ** service roads */ } } } 2.创建模型

(1)定义基类

using UnityEngine; [RequireComponent(typeof(MapReader))]//该物体应当添加了MapReader组件 abstract class InfrstructureBehaviour : MonoBehaviour { protected MapReader map; void Awake() { map = GetComponent(); } protected Vector3 GetCentre(OsmWay way) { Vector3 total = Vector3.zero; foreach (var id in way.NodeIDs) { total += map.nodes[id]; } return total / way.NodeIDs.Count; } }

(2)创建道路

using System.Collections; using System.Collections.Generic; using UnityEngine; class RoadMaker : InfrstructureBehaviour { public Material roadMaterial; IEnumerator Start() { while (!map.IsReady){yield return null;}//等待地图数据解析完毕 foreach (var way in map.ways.FindAll((w) => { return w.IsRoad; })) { //【1】创建一个游戏物体,并将它的位置设置为道路中心 GameObject go = new GameObject(); Vector3 localOrigin = GetCentre(way);//找到这条道路的中心位置 go.transform.position = localOrigin - map.bounds.Centre;//减掉整个地图的中心位置,就是应该在场景中显示的道路中心了 //【2】向游戏物体添加MeshFilter和MeshRenderer组件 MeshFilter mf = go.AddComponent(); MeshRenderer mr = go.AddComponent(); //设置材质 mr.material = roadMaterial; //设置顶点位置、法线、uv、三角形 List vectors = new List(); List normals = new List(); List uvs = new List(); List indices = new List(); for (int i = 1; i normals.Add(-Vector3.up);} //设置三角形的顶点索引 // index values int idx1, idx2,idx3, idx4; idx4 = vectors.Count - 1; idx3 = vectors.Count - 2; idx2 = vectors.Count - 3; idx1 = vectors.Count - 4; // first triangle v1, v3, v2 indices.Add(idx1); indices.Add(idx3); indices.Add(idx2); // second triangle v3, v4, v2 indices.Add(idx3); indices.Add(idx4); indices.Add(idx2); // third triangle v2, v3, v1 indices.Add(idx2); indices.Add(idx3); indices.Add(idx1); // fourth triangle v2, v4, v3 indices.Add(idx2); indices.Add(idx4); indices.Add(idx3); } mf.mesh.vertices = vectors.ToArray(); mf.mesh.normals = normals.ToArray(); mf.mesh.triangles = indices.ToArray(); mf.mesh.uv = uvs.ToArray(); yield return null; } } }

(3)创建建筑物

using System.Collections; using System.Collections.Generic; using UnityEngine; class BuildingMaker : InfrstructureBehaviour { public Material building; IEnumerator Start() { while (!map.IsReady){yield return null;} foreach (var way in map.ways.FindAll((w) => { return w.IsBuilding && w.NodeIDs.Count > 1; })) { GameObject go = new GameObject(); Vector3 localOrigin = GetCentre(way); go.transform.position = localOrigin - map.bounds.Centre; MeshFilter mf = go.AddComponent(); MeshRenderer mr = go.AddComponent(); mr.material = building; List vectors = new List(); List normals = new List(); List indices = new List(); for (int i = 1; i normals.Add(-Vector3.forward);} // index values int idx1, idx2,idx3, idx4; idx4 = vectors.Count - 1; idx3 = vectors.Count - 2; idx2 = vectors.Count - 3; idx1 = vectors.Count - 4; // first triangle v1, v3, v2 indices.Add(idx1); indices.Add(idx3); indices.Add(idx2); // second triangle v3, v4, v2 indices.Add(idx3); indices.Add(idx4); indices.Add(idx2); // third triangle v2, v3, v1 indices.Add(idx2); indices.Add(idx3); indices.Add(idx1); // fourth triangle v2, v4, v3 indices.Add(idx2); indices.Add(idx4); indices.Add(idx3); } mf.mesh.vertices = vectors.ToArray(); mf.mesh.normals = normals.ToArray(); mf.mesh.triangles = indices.ToArray(); yield return null; } } } 七、参考 使用LineRender可视化:OSM-2-Unity创建简单模型:osm-unity


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有